#include "tablewindow.h"
#include "ui_tablewindow.h"
#include "services/dbmanager.h"
#include "services/notifymanager.h"
#include "sqlitestudio.h"
#include "common/unused.h"
#include "schemaresolver.h"
#include "iconmanager.h"
#include "common/intvalidator.h"
#include "common/extlineedit.h"
#include "datagrid/sqltablemodel.h"
#include "common/extaction.h"
#include "mainwindow.h"
#include "tablestructuremodel.h"
#include "tableconstraintsmodel.h"
#include "dialogs/columndialog.h"
#include "dialogs/constraintdialog.h"
#include "mdiarea.h"
#include "sqlitesyntaxhighlighter.h"
#include "dialogs/newconstraintdialog.h"
#include "db/chainexecutor.h"
#include "common/widgetcover.h"
#include "mdiwindow.h"
#include "dbtree/dbtree.h"
#include "constrainttabmodel.h"
#include "parser/ast/sqlitecreateindex.h"
#include "parser/ast/sqlitecreatetrigger.h"
#include "dialogs/messagelistdialog.h"
#include "services/codeformatter.h"
#include "uiconfig.h"
#include "dialogs/ddlpreviewdialog.h"
#include "services/config.h"
#include "services/importmanager.h"
#include "dbobjectdialogs.h"
#include "dialogs/exportdialog.h"
#include "common/centerediconitemdelegate.h"
#include "themetuner.h"
#include "dialogs/importdialog.h"
#include "dialogs/populatedialog.h"
#include "datagrid/sqlqueryitem.h"
#include <QMenu>
#include <QToolButton>
#include <QLabel>
#include <QDebug>
#include <QMessageBox>
#include <tablemodifier.h>
#include <QProgressBar>
#include <QPushButton>
#include <QDebug>
#include <QStyleFactory>

// TODO extend QTableView for columns and constraints, so they show full-row-width drop indicator,
// instead of single column drop indicator.

CFG_KEYS_DEFINE(TableWindow)

TableWindow::TableWindow(QWidget* parent) :
    MdiChild(parent),
    ui(new Ui::TableWindow)
{
    init();
    applyInitialTab();
}

TableWindow::TableWindow(Db* db, QWidget* parent) :
    MdiChild(parent),
    db(db),
    ui(new Ui::TableWindow)
{
    newTable();
    init();
    initDbAndTable();
    applyInitialTab();
}

TableWindow::TableWindow(const TableWindow& win) :
    MdiChild(win.parentWidget()),
    db(win.db),
    database(win.database),
    table(win.table),
    ui(new Ui::TableWindow)
{
    init();
    initDbAndTable();
    applyInitialTab();
}

TableWindow::TableWindow(QWidget *parent, Db* db, const QString& database, const QString& table) :
    MdiChild(parent),
    db(db),
    database(database),
    table(table),
    ui(new Ui::TableWindow)
{
    init();
    initDbAndTable();
    applyInitialTab();
}

TableWindow::~TableWindow()
{
    delete ui;

    if (tableModifier)
    {
        delete tableModifier;
        tableModifier = nullptr;
    }
}

void TableWindow::staticInit()
{
    qRegisterMetaType<TableWindow>("TableWindow");
}

void TableWindow::insertAction(ExtActionPrototype* action, TableWindow::ToolBar toolbar)
{
    return ExtActionContainer::insertAction<TableWindow>(action, toolbar);
}

void TableWindow::insertActionBefore(ExtActionPrototype* action, TableWindow::Action beforeAction, TableWindow::ToolBar toolbar)
{
    return ExtActionContainer::insertActionBefore<TableWindow>(action, beforeAction, toolbar);
}

void TableWindow::insertActionAfter(ExtActionPrototype* action, TableWindow::Action afterAction, TableWindow::ToolBar toolbar)
{
    return ExtActionContainer::insertActionAfter<TableWindow>(action, afterAction, toolbar);
}

void TableWindow::removeAction(ExtActionPrototype* action, TableWindow::ToolBar toolbar)
{
    ExtActionContainer::removeAction<TableWindow>(action, toolbar);
}

void TableWindow::newTable()
{
    existingTable = false;
    table = "";
}

void TableWindow::init()
{
    ui->setupUi(this);
    ui->structureSplitter->setStretchFactor(0, 2);
    ui->structureView->horizontalHeader()->setSectionsClickable(false);
    ui->structureView->verticalHeader()->setSectionsClickable(false);
    constraintColumnsDelegate = new CenteredIconItemDelegate(this);

#ifdef Q_OS_MACX
    QStyle *fusion = QStyleFactory::create("Fusion");
    ui->structureToolBar->setStyle(fusion);
    ui->structureTab->layout()->setSpacing(0);
    ui->tableConstraintsToolbar->setStyle(fusion);
    ui->constraintsWidget->layout()->setSpacing(0);
    ui->indexToolBar->setStyle(fusion);
    ui->indexesTab->layout()->setSpacing(0);
    ui->triggerToolBar->setStyle(fusion);
    ui->triggersTab->layout()->setSpacing(0);
#endif

    dataModel = new SqlTableModel(this);
    ui->dataView->init(dataModel);

    initActions();
    updateTabsOrder();

    connect(dataModel, SIGNAL(executionSuccessful()), this, SLOT(executionSuccessful()));
    connect(dataModel, SIGNAL(executionFailed(QString)), this, SLOT(executionFailed(QString)));
    connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int)));
    connect(this, SIGNAL(modifyStatusChanged()), this, SLOT(updateStructureCommitState()));
    connect(ui->tableNameEdit, SIGNAL(textChanged(QString)), this, SIGNAL(modifyStatusChanged()));
    connect(ui->tableNameEdit, SIGNAL(textChanged(QString)), this, SLOT(nameChanged()));
    connect(ui->indexList, SIGNAL(itemSelectionChanged()), this, SLOT(updateIndexesState()));
    connect(ui->triggerList, SIGNAL(itemSelectionChanged()), this, SLOT(updateTriggersState()));
    connect(CFG_UI.General.DataTabAsFirstInTables, SIGNAL(changed(const QVariant&)), this, SLOT(updateTabsOrder()));
    connect(ui->structureView, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(structureViewDoubleClicked(const QModelIndex&)));
    connect(ui->tableConstraintsView, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(constraintsViewDoubleClicked(const QModelIndex&)));
    connect(CFG_UI.Fonts.DataView, SIGNAL(changed(QVariant)), this, SLOT(updateFont()));

    structureExecutor = new ChainExecutor(this);
    connect(structureExecutor, SIGNAL(success(SqlQueryPtr)), this, SLOT(changesSuccessfullyCommitted()));
    connect(structureExecutor, SIGNAL(failure(int,QString)), this, SLOT(changesFailedToCommit(int,QString)));

    THEME_TUNER->manageCompactLayout({
                                         ui->structureTab,
                                         ui->constraintsWidget,
                                         ui->dataTab,
                                         ui->constraintsTab,
                                         ui->indexesTab,
                                         ui->triggersTab,
                                         ui->ddlTab
                                     });

    updateFont();
    setupCoverWidget();
    updateAfterInit();
}

void TableWindow::createActions()
{
    createAction(EXPORT, ICONS.TABLE_EXPORT, tr("Export table", "table window"), this, SLOT(exportTable()), this);
    createAction(IMPORT, ICONS.TABLE_IMPORT, tr("Import data to table", "table window"), this, SLOT(importTable()), this);
    createAction(POPULATE, ICONS.TABLE_POPULATE, tr("Populate table", "table window"), this, SLOT(populateTable()), this);

    createStructureActions();
    createDataGridActions();
    createDataFormActions();
    createIndexActions();
    createTriggerActions();

    createAction(NEXT_TAB, "next tab", this, SLOT(nextTab()), this);
    createAction(PREV_TAB, "prev tab", this, SLOT(prevTab()), this);
}

void TableWindow::createStructureActions()
{
    createAction(REFRESH_STRUCTURE, ICONS.RELOAD, tr("Refresh structure", "table window"), this, SLOT(refreshStructure()), ui->structureToolBar);
    ui->structureToolBar->addSeparator();
    createAction(COMMIT_STRUCTURE, ICONS.COMMIT, tr("Commit structure changes", "table window"), this, SLOT(commitStructure()), ui->structureToolBar);
    createAction(ROLLBACK_STRUCTURE, ICONS.ROLLBACK, tr("Rollback structure changes", "table window"), this, SLOT(rollbackStructure()), ui->structureToolBar);
    createAction(ADD_COLUMN, ICONS.TABLE_COLUMN_ADD, tr("Add column", "table window"), this, SLOT(addColumn()), ui->structureToolBar, ui->structureView);
    createAction(EDIT_COLUMN, ICONS.TABLE_COLUMN_EDIT, tr("Edit column", "table window"), this, SLOT(editColumn()), ui->structureToolBar, ui->structureView);
    createAction(DEL_COLUMN, ICONS.TABLE_COLUMN_DELETE, tr("Delete column", "table window"), this, SLOT(delColumn()), ui->structureToolBar, ui->structureView);
    createAction(MOVE_COLUMN_UP, ICONS.MOVE_UP, tr("Move column up", "table window"), this, SLOT(moveColumnUp()), ui->structureToolBar, ui->structureView);
    createAction(MOVE_COLUMN_DOWN, ICONS.MOVE_DOWN, tr("Move column down", "table window"), this, SLOT(moveColumnDown()), ui->structureToolBar, ui->structureView);
    ui->structureToolBar->addSeparator();
    ui->structureToolBar->addAction(actionMap[IMPORT]);
    ui->structureToolBar->addAction(actionMap[EXPORT]);
    ui->structureToolBar->addAction(actionMap[POPULATE]);
    ui->structureToolBar->addSeparator();
    createAction(CREATE_SIMILAR, ICONS.TABLE_CREATE_SIMILAR, tr("Create similar table", "table window"), this, SLOT(createSimilarTable()), ui->structureToolBar);
    createAction(RESET_AUTOINCREMENT, ICONS.RESET_AUTOINCREMENT, tr("Reset autoincrement value", "table window"), this, SLOT(resetAutoincrement()), ui->structureToolBar);

    // Table constraints
    createAction(ADD_TABLE_CONSTRAINT, ICONS.TABLE_CONSTRAINT_ADD, tr("Add table constraint", "table window"), this, SLOT(addConstraint()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
    createAction(EDIT_TABLE_CONSTRAINT, ICONS.TABLE_CONSTRAINT_EDIT, tr("Edit table constraint", "table window"), this, SLOT(editConstraint()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
    createAction(DEL_TABLE_CONSTRAINT, ICONS.TABLE_COLUMN_DELETE, tr("Delete table constraint", "table window"), this, SLOT(delConstraint()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
    createAction(MOVE_CONSTRAINT_UP, ICONS.MOVE_UP, tr("Move table constraint up", "table window"), this, SLOT(moveConstraintUp()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
    createAction(MOVE_CONSTRAINT_DOWN, ICONS.MOVE_DOWN, tr("Move table constraint down", "table window"), this, SLOT(moveConstraintDown()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
    ui->tableConstraintsToolbar->addSeparator();
    createAction(ADD_TABLE_PK, ICONS.CONSTRAINT_PRIMARY_KEY_ADD, tr("Add table primary key", "table window"), this, SLOT(addPk()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
    createAction(ADD_TABLE_FK, ICONS.CONSTRAINT_FOREIGN_KEY_ADD, tr("Add table foreign key", "table window"), this, SLOT(addFk()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
    createAction(ADD_TABLE_UNIQUE, ICONS.CONSTRAINT_UNIQUE_ADD, tr("Add table unique constraint", "table window"), this, SLOT(addUnique()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
    createAction(ADD_TABLE_CHECK, ICONS.CONSTRAINT_CHECK_ADD, tr("Add table check constraint", "table window"), this, SLOT(addCheck()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
}

void TableWindow::createDataGridActions()
{
    QAction* before = ui->dataView->getAction(DataView::FILTER_VALUE);
    ui->dataView->getToolBar(DataView::TOOLBAR_GRID)->insertAction(before, actionMap[IMPORT]);
    ui->dataView->getToolBar(DataView::TOOLBAR_GRID)->insertAction(before, actionMap[EXPORT]);
    ui->dataView->getToolBar(DataView::TOOLBAR_GRID)->insertAction(before, actionMap[POPULATE]);
    ui->dataView->getToolBar(DataView::TOOLBAR_GRID)->insertSeparator(before);
}

void TableWindow::createDataFormActions()
{
}

void TableWindow::createIndexActions()
{
    createAction(REFRESH_INDEXES, ICONS.RELOAD, tr("Refresh index list", "table window"), this, SLOT(updateIndexes()), ui->indexToolBar, ui->indexList);
    ui->indexToolBar->addSeparator();
    createAction(ADD_INDEX, ICONS.INDEX_ADD, tr("Create index", "table window"), this, SLOT(addIndex()), ui->indexToolBar, ui->indexList);
    createAction(EDIT_INDEX, ICONS.INDEX_EDIT, tr("Edit index", "table window"), this, SLOT(editCurrentIndex()), ui->indexToolBar, ui->indexList);
    createAction(DEL_INDEX, ICONS.INDEX_DEL, tr("Delete index", "table window"), this, SLOT(delIndex()), ui->indexToolBar, ui->indexList);
    connect(ui->indexList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(indexViewDoubleClicked(QModelIndex)));
}

void TableWindow::createTriggerActions()
{
    createAction(REFRESH_TRIGGERS, ICONS.RELOAD, tr("Refresh trigger list", "table window"), this, SLOT(updateTriggers()), ui->triggerToolBar, ui->triggerList);
    ui->triggerToolBar->addSeparator();
    createAction(ADD_TRIGGER, ICONS.TRIGGER_ADD, tr("Create trigger", "table window"), this, SLOT(addTrigger()), ui->triggerToolBar, ui->triggerList);
    createAction(EDIT_TRIGGER, ICONS.TRIGGER_EDIT, tr("Edit trigger", "table window"), this, SLOT(editTrigger()), ui->triggerToolBar, ui->triggerList);
    createAction(DEL_TRIGGER, ICONS.TRIGGER_DEL, tr("Delete trigger", "table window"), this, SLOT(delTrigger()), ui->triggerToolBar, ui->triggerList);
    connect(ui->triggerList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(triggerViewDoubleClicked(QModelIndex)));
}

void TableWindow::editColumn(const QModelIndex& idx)
{
    if (!idx.isValid())
    {
        addColumn();
        return;
    }

    SqliteCreateTable::Column* column = structureModel->getColumn(idx.row());
    ColumnDialog columnDialog(db, this);
    columnDialog.setColumn(column);
    if (hasAnyPkDefined() && !column->hasConstraint(SqliteCreateTable::Column::Constraint::PRIMARY_KEY))
        columnDialog.disableConstraint(ConstraintDialog::Constraint::PK);

    if (columnDialog.exec() != QDialog::Accepted)
        return;

    SqliteCreateTable::Column* modifiedColumn = columnDialog.getModifiedColumn();
    structureModel->replaceColumn(idx.row(), modifiedColumn);
    resizeStructureViewColumns();
    updateTableConstraintsToolbarState();
}

void TableWindow::delColumn(const QModelIndex& idx)
{
    if (!idx.isValid())
    {
        qWarning() << "Called TableWindow::delColumn() with invalid index.";
        return;
    }

    SqliteCreateTable::Column* column = structureModel->getColumn(idx.row());

    QString msg = tr("Are you sure you want to delete column '%1'?", "table window").arg(column->name);
    int btn = QMessageBox::question(this, tr("Delete column", "table window"), msg);
    if (btn != QMessageBox::Yes)
        return;

    structureModel->delColumn(idx.row());
    resizeStructureViewColumns();
    updateTableConstraintsToolbarState();
}

void TableWindow::executeStructureChanges()
{
    QStringList sqls;

    createTable->rebuildTokens();
    if (!existingTable)
    {
        sqls << createTable->detokenize();
    }
    else
    {
        if (tableModifier)
            delete tableModifier;

        tableModifier = new TableModifier(db, database, table);
        tableModifier->alterTable(createTable);

        if (tableModifier->hasMessages())
        {
            MessageListDialog dialog(tr("Following problems will take place while modifying the table.\n"
                                        "Would you like to proceed?", "table window"));
            dialog.setWindowTitle(tr("Table modification", "table window"));
            for (const QString& error : tableModifier->getErrors())
                dialog.addError(error);

            for (const QString& warn : tableModifier->getWarnings())
                dialog.addWarning(warn);

            if (dialog.exec() != QDialog::Accepted)
                return;
        }

        sqls = tableModifier->generateSqls();
    }

    if (!CFG_UI.General.DontShowDdlPreview.get())
    {
        DdlPreviewDialog dialog(db, this);
        dialog.setDdl(sqls);
        if (dialog.exec() != QDialog::Accepted)
            return;
    }

    modifyingThisTable = true;
    structureExecutor->setDb(db);
    structureExecutor->setQueries(sqls);
    structureExecutor->setDisableForeignKeys(true);
    structureExecutor->setDisableObjectDropsDetection(true);
    widgetCover->show();
    structureExecutor->exec();
}

void TableWindow::updateAfterInit()
{
    updateStructureCommitState();
    updateStructureToolbarState();
    updateTableConstraintsToolbarState();
    updateNewTableState();
    updateIndexesState();
    updateTriggersState();
}

QModelIndex TableWindow::structureCurrentIndex() const
{
    return ui->structureView->selectionModel()->currentIndex();
}

void TableWindow::updateStructureToolbarState()
{
    QItemSelectionModel *selModel = ui->structureView->selectionModel();
    bool validIdx = false;
    bool isFirst = false;
    bool isLast = false;
    if (selModel)
    {
        QModelIndex currIdx = selModel->currentIndex();
        if (currIdx.isValid())
        {
            validIdx = true;
            if (currIdx.row() == 0)
                isFirst = true;

            if (currIdx.row() == (structureModel->rowCount() - 1))
                isLast = true;
        }
    }

    actionMap[EDIT_COLUMN]->setEnabled(validIdx);
    actionMap[DEL_COLUMN]->setEnabled(validIdx);
    actionMap[MOVE_COLUMN_UP]->setEnabled(validIdx && !isFirst);
    actionMap[MOVE_COLUMN_DOWN]->setEnabled(validIdx && !isLast);
}

void TableWindow::updateStructureCommitState()
{
    bool modified = isModified();
    actionMap[COMMIT_STRUCTURE]->setEnabled(modified);
    actionMap[ROLLBACK_STRUCTURE]->setEnabled(modified && existingTable);
}

void TableWindow::updateTableConstraintsToolbarState()
{
    QItemSelectionModel *selModel = ui->tableConstraintsView->selectionModel();
    bool anyColumn = structureModel && structureModel->rowCount() > 0;
    bool validIdx = false;
    bool isFirst = false;
    bool isLast = false;
    if (selModel)
    {
        QModelIndex currIdx = selModel->currentIndex();
        if (currIdx.isValid())
        {
            validIdx = true;
            if (currIdx.row() == 0)
                isFirst = true;

            if (currIdx.row() == (structureConstraintsModel->rowCount() - 1))
                isLast = true;
        }
    }

    actionMap[EDIT_TABLE_CONSTRAINT]->setEnabled(anyColumn && validIdx);
    actionMap[DEL_TABLE_CONSTRAINT]->setEnabled(anyColumn && validIdx);
    actionMap[MOVE_CONSTRAINT_UP]->setEnabled(anyColumn && validIdx && !isFirst);
    actionMap[MOVE_CONSTRAINT_DOWN]->setEnabled(anyColumn && validIdx && !isLast);
    actionMap[ADD_TABLE_PK]->setEnabled(!hasAnyPkDefined());
}

void TableWindow::setupDefShortcuts()
{
    // Widget context
    setShortcutContext({
                           REFRESH_STRUCTURE,
                           REFRESH_INDEXES,
                           REFRESH_TRIGGERS,
                           ADD_COLUMN,
                           EDIT_COLUMN,
                           DEL_COLUMN,
                           ADD_TABLE_CONSTRAINT,
                           EDIT_TABLE_CONSTRAINT,
                           DEL_TABLE_CONSTRAINT,
                           ADD_INDEX,
                           EDIT_INDEX,
                           DEL_INDEX,
                           ADD_TRIGGER,
                           EDIT_TRIGGER,
                           DEL_TRIGGER,
                       },
                       Qt::WidgetWithChildrenShortcut);

    BIND_SHORTCUTS(TableWindow, Action);
}

void TableWindow::executionSuccessful()
{
    dataLoaded = true;
}

void TableWindow::executionFailed(const QString& errorText)
{
    notifyError(tr("Could not load data for table %1. Error details: %2").arg(table).arg(errorText));
}

void TableWindow::initDbAndTable()
{
    int totalConstrCols = 6;
    if (db->getVersion() == 2)
    {
        ui->withoutRowIdCheck->setVisible(false);
        totalConstrCols -= 2;
    }

    totalConstrCols += 2; // we start at 3rd column
    for (int colIdx = 2; colIdx < totalConstrCols; colIdx++)
        ui->structureView->setItemDelegateForColumn(colIdx, constraintColumnsDelegate);

    if (existingTable)
    {
        dataModel->setDb(db);
        dataModel->setDatabaseAndTable(database, table);
    }

    ui->tableNameEdit->setText(table); // TODO no attached/temp db name support here

    if (structureModel)
    {
        delete structureModel;
        structureModel = nullptr;
    }

    if (structureConstraintsModel)
    {
        delete structureConstraintsModel;
        structureConstraintsModel = nullptr;
    }

    if (constraintTabModel)
    {
        delete constraintTabModel;
        constraintTabModel = nullptr;
    }

    structureModel = new TableStructureModel(this);
    structureConstraintsModel = new TableConstraintsModel(this);
    constraintTabModel = new ConstraintTabModel(this);

    // Columns model signals
    connect(structureModel, SIGNAL(columnModified(QString,SqliteCreateTable::Column*)),
            structureConstraintsModel, SLOT(columnModified(QString,SqliteCreateTable::Column*)));
    connect(structureModel, SIGNAL(columnDeleted(QString)),
            structureConstraintsModel, SLOT(columnDeleted(QString)));
    connect(structureModel, SIGNAL(columnsOrderChanged()), this, SLOT(updateStructureToolbarState()));

    connect(structureModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(updateDdlTab()));
    connect(structureModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(updateDdlTab()));
    connect(structureModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(updateDdlTab()));
    connect(structureModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateDdlTab()));
    connect(structureModel, SIGNAL(modifiyStateChanged()), this, SIGNAL(modifyStatusChanged()));

    ui->structureView->setModel(structureModel);
    ui->structureView->verticalHeader()->setDefaultSectionSize(ui->structureView->fontMetrics().height() + 8);

    // Constraints model signals
    connect(structureConstraintsModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(updateDdlTab()));
    connect(structureConstraintsModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(updateDdlTab()));
    connect(structureConstraintsModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(updateDdlTab()));
    connect(structureConstraintsModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateDdlTab()));

    connect(structureConstraintsModel, SIGNAL(modifiyStateChanged()), this, SIGNAL(modifyStatusChanged()));
    connect(structureConstraintsModel, SIGNAL(constraintOrderChanged()), this, SLOT(updateTableConstraintsToolbarState()));

    ui->tableConstraintsView->setModel(structureConstraintsModel);
    ui->tableConstraintsView->verticalHeader()->setDefaultSectionSize(ui->tableConstraintsView->fontMetrics().height() + 8);

    // Constraint tab model signals
    connect(structureModel, SIGNAL(rowsInserted(QModelIndex,int,int)), constraintTabModel, SLOT(updateModel()));
    connect(structureModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), constraintTabModel, SLOT(updateModel()));
    connect(structureModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), constraintTabModel, SLOT(updateModel()));
    connect(structureModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), constraintTabModel, SLOT(updateModel()));
    connect(structureConstraintsModel, SIGNAL(rowsInserted(QModelIndex,int,int)), constraintTabModel, SLOT(updateModel()));
    connect(structureConstraintsModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), constraintTabModel, SLOT(updateModel()));
    connect(structureConstraintsModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), constraintTabModel, SLOT(updateModel()));
    connect(structureConstraintsModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), constraintTabModel, SLOT(updateModel()));

    ui->constraintsView->setModel(constraintTabModel);

    connect(ui->withoutRowIdCheck, SIGNAL(clicked()), this, SLOT(withOutRowIdChanged()));

    ui->ddlEdit->setSqliteVersion(db->getVersion());
    parseDdl();
    updateIndexes();
    updateTriggers();

    // (Re)connect to DB signals
    connect(db, SIGNAL(dbObjectDeleted(QString,QString,DbObjectType)), this, SLOT(checkIfTableDeleted(QString,QString,DbObjectType)));

    // Selection model is recreated when setModel() is called on the view
    connect(ui->structureView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
            this, SLOT(updateStructureToolbarState()));
    connect(ui->tableConstraintsView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
            this, SLOT(updateTableConstraintsToolbarState()));
}

void TableWindow::setupCoverWidget()
{
    widgetCover = new WidgetCover(this);
    widgetCover->initWithInterruptContainer();
    widgetCover->hide();
    connect(widgetCover, SIGNAL(cancelClicked()), structureExecutor, SLOT(interrupt()));
}

void TableWindow::parseDdl()
{
    if (existingTable)
    {
        SchemaResolver resolver(db);
        SqliteQueryPtr parsedObject = resolver.getParsedObject(database, table, SchemaResolver::TABLE);
        if (!parsedObject.dynamicCast<SqliteCreateTable>())
        {
            notifyError(tr("Could not process the %1 table correctly. Unable to open a table window.").arg(table));
            invalid = true;
            return;
        }

        createTable = parsedObject.dynamicCast<SqliteCreateTable>();
    }
    else
    {
        createTable = SqliteCreateTablePtr::create();
        createTable->table = table;
        createTable->dialect = db->getDialect();
    }
    originalCreateTable = SqliteCreateTablePtr::create(*createTable);
    structureModel->setCreateTable(createTable.data());
    structureConstraintsModel->setCreateTable(createTable.data());
    constraintTabModel->setCreateTable(createTable.data());
    ui->withoutRowIdCheck->setChecked(!createTable->withOutRowId.isNull());
    ui->tableConstraintsView->resizeColumnsToContents();
    ui->structureView->resizeColumnsToContents();
    ui->constraintsView->resizeColumnsToContents();

    updateStructureToolbarState();
    updateTableConstraintsToolbarState();
    updateDdlTab();
}

void TableWindow::changeEvent(QEvent *e)
{
    QWidget::changeEvent(e);
    switch (e->type()) {
        case QEvent::LanguageChange:
            ui->retranslateUi(this);
            break;
        default:
            break;
    }
}

QVariant TableWindow::saveSession()
{
    if (!db || DBLIST->isTemporary(db))
        return QVariant();

    QHash<QString,QVariant> sessionValue;
    sessionValue["table"] = table;
    sessionValue["db"] = db->getName();
    return sessionValue;
}

bool TableWindow::restoreSession(const QVariant& sessionValue)
{
    QHash<QString, QVariant> value = sessionValue.toHash();
    if (value.size() == 0)
    {
        notifyWarn(tr("Could not restore window %1, because no database or table was stored in session for this window.").arg(value["title"].toString()));
        return false;
    }

    if (!value.contains("db") || !value.contains("table"))
    {
        notifyWarn(tr("Could not restore window '%1', because no database or table was stored in session for this window.").arg(value["title"].toString()));
        return false;
    }

    db = DBLIST->getByName(value["db"].toString());
    if (!db || !db->isValid() || (!db->isOpen() && !db->open()))
    {
        notifyWarn(tr("Could not restore window '%1', because database %2 could not be resolved.").arg(value["title"].toString(), value["db"].toString()));
        return false;
    }

    table = value["table"].toString();
    database = value["database"].toString();
    SchemaResolver resolver(db);
    if (!resolver.getTables(database).contains(table, Qt::CaseInsensitive))
    {
        notifyWarn(tr("Could not restore window '%1'', because the table %2 doesn't exist in the database %3.").arg(value["title"].toString(), table, db->getName()));
        return false;
    }

    initDbAndTable();
    applyInitialTab();
    return true;
}

Icon* TableWindow::getIconNameForMdiWindow()
{
    return ICONS.TABLE;
}

QString TableWindow::getTitleForMdiWindow()
{
    QString dbSuffix = (!db ? "" : (" (" + db->getName() + ")"));
    if (existingTable)
        return table + dbSuffix;

    QStringList existingNames = MainWindow::getInstance()->getMdiArea()->getWindowTitles();
    if (existingNames.contains(windowTitle()))
        return windowTitle();

    // Generate new name
    QString title = tr("New table %1").arg(newTableWindowNum++);
    while (existingNames.contains(title))
        title = tr("New table %1").arg(newTableWindowNum++);

    title += dbSuffix;
    return title;
}

Db* TableWindow::getDb() const
{
    return db;
}

QString TableWindow::getTable() const
{
    return table;
}

void TableWindow::dbClosedFinalCleanup()
{
    db = nullptr;
    dataModel->setDb(nullptr);
    structureExecutor->setDb(nullptr);
}

void TableWindow::checkIfTableDeleted(const QString& database, const QString& object, DbObjectType type)
{
    UNUSED(database);

    // TODO uncomment below when dbnames are supported
//    if (this->database != database)
//        return;

    switch (type)
    {
        case DbObjectType::TABLE:
            break;
        case DbObjectType::INDEX:
            checkIfIndexDeleted(object);
            return;
        case DbObjectType::TRIGGER:
            checkIfTriggerDeleted(object);
            return;
        case DbObjectType::VIEW:
            return;
    }

    if (modifyingThisTable)
        return;

    if (object.compare(table, Qt::CaseInsensitive) == 0)
    {
        dbClosedFinalCleanup();
        getMdiWindow()->close();
    }
}

void TableWindow::checkIfIndexDeleted(const QString& object)
{
    for (int i = 0, total = ui->indexList->rowCount(); i < total; ++i)
    {
        if (ui->indexList->item(i, 0)->text().compare(object, Qt::CaseInsensitive) == 0)
        {
            ui->indexList->removeRow(i);
            return;
        }
    }
}

void TableWindow::checkIfTriggerDeleted(const QString& object)
{
    for (int i = 0, total = ui->triggerList->rowCount(); i < total; ++i)
    {
        if (ui->triggerList->item(i, 0)->text().compare(object, Qt::CaseInsensitive) == 0)
        {
            ui->triggerList->removeRow(i);
            return;
        }
    }
}

void TableWindow::refreshStructure()
{
    parseDdl();
    updateIndexes();
    updateTriggers();
}

void TableWindow::commitStructure(bool skipWarning)
{
    if (!isModified())
    {
        qWarning() << "Called TableWindow::commitStructure(), but isModified() returned false.";
        updateStructureCommitState();
        return;
    }

    if (!validate(skipWarning))
        return;

    executeStructureChanges();
}

void TableWindow::changesSuccessfullyCommitted()
{
    modifyingThisTable = false;

    QStringList sqls = structureExecutor->getQueries();
    CFG->addDdlHistory(sqls.join("\n"), db->getName(), db->getPath());

    widgetCover->hide();

    originalCreateTable = createTable;
    structureModel->setCreateTable(createTable.data());
    structureConstraintsModel->setCreateTable(createTable.data());
    dataLoaded = false;

    QString oldTable = table;
    database = createTable->database;
    table = createTable->table;
    existingTable = true;
    initDbAndTable();
    updateStructureCommitState();
    updateNewTableState();
    updateWindowTitle();

    NotifyManager* notifyManager = NotifyManager::getInstance();
    if (oldTable.compare(table, Qt::CaseInsensitive) == 0 || oldTable.isEmpty())
    {
        notifyInfo(tr("Committed changes for table '%1' successfully.").arg(table));
    }
    else
    {
        notifyInfo(tr("Committed changes for table '%1' (named before '%2') successfully.").arg(table, oldTable));
        notifyManager->renamed(db, database, oldTable, table);
    }
    notifyManager->modified(db, database, table);

    DBTREE->refreshSchema(db);

    if (tableModifier)
    {
        QList<QStringList> modifiedObjects = {
            tableModifier->getModifiedTables(),
            tableModifier->getModifiedIndexes(),
            tableModifier->getModifiedTriggers(),
            tableModifier->getModifiedViews()
        };
        for (const QStringList& objList : modifiedObjects)
        {
            for (const QString& obj : objList)
            {
                if (obj.compare(oldTable, Qt::CaseInsensitive) == 0)
                    continue;

                notifyManager->modified(db, database, obj);
            }
        }
    }
}

void TableWindow::changesFailedToCommit(int errorCode, const QString& errorText)
{
    qDebug() << "TableWindow::changesFailedToCommit:" << errorCode << errorText;

    modifyingThisTable = false;
    widgetCover->hide();
    notifyError(tr("Could not commit table structure. Error message: %1", "table window").arg(errorText));
}

void TableWindow::rollbackStructure()
{
    createTable = SqliteCreateTablePtr::create(*originalCreateTable.data());
    structureModel->setCreateTable(createTable.data());
    structureConstraintsModel->setCreateTable(createTable.data());
    constraintTabModel->setCreateTable(createTable.data());
    ui->tableNameEdit->setText(createTable->table);

    updateStructureCommitState();
    updateStructureToolbarState();
    updateTableConstraintsToolbarState();
    updateDdlTab();
}

void TableWindow::resetAutoincrement()
{
    if (!existingTable)
        return;

    QMessageBox::StandardButton btn = QMessageBox::question(this, tr("Reset autoincrement"), tr("Are you sure you want to reset autoincrement value for table '%1'?")
                                                            .arg(table));
    if (btn != QMessageBox::Yes)
        return;

    SqlQueryPtr res = db->exec("DELETE FROM sqlite_sequence WHERE name = ?;", {table});
    if (res->isError())
        notifyError(tr("An error occurred while trying to reset autoincrement value for table '%1': %2").arg(table, res->getErrorText()));
    else
        notifyInfo(tr("Autoincrement value for table '%1' has been reset successfully.").arg(table));
}

void TableWindow::addColumn()
{
    SqliteCreateTable::Column column;
    column.setParent(createTable.data());

    ColumnDialog columnDialog(db, this);
    columnDialog.setColumn(&column);
    if (hasAnyPkDefined())
        columnDialog.disableConstraint(ConstraintDialog::Constraint::PK);

    if (columnDialog.exec() != QDialog::Accepted)
        return;

    SqliteCreateTable::Column* modifiedColumn = columnDialog.getModifiedColumn();
    structureModel->appendColumn(modifiedColumn);
    ui->structureView->resizeColumnToContents(0);

    ui->structureView->setCurrentIndex(structureModel->index(structureModel->rowCount()-1, 0));
    resizeStructureViewColumns();
    updateTableConstraintsToolbarState();
}

void TableWindow::editColumn()
{
    editColumn(structureCurrentIndex());
}

void TableWindow::delColumn()
{
    QModelIndex idx = structureCurrentIndex();
    delColumn(idx);
}

void TableWindow::moveColumnUp()
{
    QModelIndex idx = structureCurrentIndex();
    if (!idx.isValid())
    {
        qWarning() << "Called TableWindow::moveColumnUp() with invalid index.";
        return;
    }

    structureModel->moveColumnUp(idx.row());
}

void TableWindow::moveColumnDown()
{
    QModelIndex idx = structureCurrentIndex();
    if (!idx.isValid())
    {
        qWarning() << "Called TableWindow::moveColumnDown() with invalid index.";
        return;
    }

    structureModel->moveColumnDown(idx.row());
}


void TableWindow::addConstraint(ConstraintDialog::Constraint mode)
{
    NewConstraintDialog dialog(mode, createTable.data(), db, this);
    if (hasAnyPkDefined())
        dialog.disableMode(ConstraintDialog::PK);

    if (dialog.exec() != QDialog::Accepted)
        return;

    SqliteStatement* constrStmt = dialog.getConstraint();
    SqliteCreateTable::Constraint* tableConstr = dynamic_cast<SqliteCreateTable::Constraint*>(constrStmt);
    if (!tableConstr)
    {
        qCritical() << "Constraint returned from ConstraintDialog was not of table type, while we're trying to add table constraint.";
        return;
    }

    structureConstraintsModel->appendConstraint(tableConstr);
    ui->tableConstraintsView->resizeColumnToContents(0);
    ui->tableConstraintsView->resizeColumnToContents(1);
    updateTableConstraintsToolbarState();
}

bool TableWindow::validate(bool skipWarning)
{
    if (!existingTable && !skipWarning && ui->tableNameEdit->text().isEmpty())
    {
        int res = QMessageBox::warning(this, tr("Empty name"), tr("A blank name for the table is allowed in SQLite, but it is not recommended.\n"
            "Are you sure you want to create a table with blank name?"), QMessageBox::Yes, QMessageBox::No);

        if (res != QMessageBox::Yes)
            return false;
    }

    if (structureModel->rowCount() == 0)
    {
        notifyError(tr("Cannot create a table without at least one column."));
        return false;
    }

    if (ui->withoutRowIdCheck->isChecked())
    {
        bool hasPk = false;
        bool isPkAutoIncr = false;

        if (createTable->getConstraints(SqliteCreateTable::Constraint::PRIMARY_KEY).size() > 0)
            hasPk = true;

        SqliteCreateTable::Column::Constraint* colConstraint = nullptr;
        for (SqliteCreateTable::Column* column : createTable->columns)
        {
            colConstraint = column->getConstraint(SqliteCreateTable::Column::Constraint::PRIMARY_KEY);
            if (colConstraint)
            {
                hasPk = true;
                if (colConstraint->autoincrKw)
                    isPkAutoIncr = true;
            }
        }

        if (!hasPk)
        {
            notifyError(tr("Cannot create table %1, if it has no primary key defined."
                           " Either uncheck the %2, or define a primary key.").arg("WITHOUT ROWID", "WITHOUT ROWID"));
            return false;
        }

        if (isPkAutoIncr)
        {
            notifyError(tr("Cannot use autoincrement for primary key when %1 clause is used."
                           " Either uncheck the %2, or the autoincrement in a primary key.").arg("WITHOUT ROWID", "WITHOUT ROWID"));
            return false;
        }
    }

    return true;
}

bool TableWindow::isModified() const
{
    return (structureModel && structureModel->isModified()) ||
            (structureConstraintsModel && structureConstraintsModel->isModified()) ||
            (originalCreateTable &&
                (originalCreateTable->table != ui->tableNameEdit->text() ||
                 originalCreateTable->withOutRowId != createTable->withOutRowId)
            ) ||
            !existingTable;
}

TokenList TableWindow::indexColumnTokens(SqliteCreateIndexPtr index)
{
    if (index->indexedColumns.size() == 0)
        return TokenList();

    SqliteOrderBy* firstCol = index->indexedColumns.first();
    SqliteOrderBy* lastCol = index->indexedColumns.last();
    if (firstCol->tokens.size() == 0)
        return TokenList();

    if (lastCol->tokens.size() == 0)
        return TokenList();

    int firstIdx = index->tokens.indexOf(firstCol->tokens.first());
    int lastIdx = index->tokens.indexOf(lastCol->tokens.last());

    return index->tokens.mid(firstIdx, lastIdx-firstIdx+1);
}

QString TableWindow::getCurrentIndex() const
{
    int row = ui->indexList->currentRow();
    QTableWidgetItem* item = ui->indexList->item(row, 0);
    if (!item)
        return QString();

    return item->text();
}

QString TableWindow::getCurrentTrigger() const
{
    int row = ui->triggerList->currentRow();
    QTableWidgetItem* item = ui->triggerList->item(row, 0);
    if (!item)
        return QString();

    return item->text();
}

void TableWindow::applyInitialTab()
{
    if (existingTable && !table.isNull() && CFG_UI.General.OpenTablesOnData.get())
        ui->tabWidget->setCurrentIndex(getDataTabIdx());
    else
        ui->tabWidget->setCurrentIndex(getStructureTabIdx());
}

void TableWindow::resizeStructureViewColumns()
{
    // Resize all except last one, to avoid shrinking the "extend to end" column.
    for (int c = 0, total = (ui->structureView->horizontalHeader()->count() - 1); c < total; ++c)
        ui->structureView->resizeColumnToContents(c);
}

int TableWindow::getDataTabIdx() const
{
    return ui->tabWidget->indexOf(ui->dataTab);
}

int TableWindow::getStructureTabIdx() const
{
    return ui->tabWidget->indexOf(ui->structureTab);
}

bool TableWindow::hasAnyPkDefined() const
{
    if (structureConstraintsModel)
    {
        for (int i = 0, total = structureConstraintsModel->rowCount(); i < total; ++i)
        {
            SqliteCreateTable::Constraint* constraint = structureConstraintsModel->getConstraint(i);
            if (constraint->type == SqliteCreateTable::Constraint::PRIMARY_KEY)
                return true;
        }
    }

    if (structureModel)
    {
        for (int i = 0, total = structureModel->rowCount(); i < total; ++i)
        {
            SqliteCreateTable::Column* column = structureModel->getColumn(i);
            if (column->hasConstraint(SqliteCreateTable::Column::Constraint::PRIMARY_KEY))
                return true;
        }
    }

    return false;
}

void TableWindow::updateDdlTab()
{
    createTable->rebuildTokens();
    QString ddl = createTable->detokenize();
    if (createTable->columns.size() > 0)
        ddl = SQLITESTUDIO->getCodeFormatter()->format("sql", ddl, db);

    ui->ddlEdit->setPlainText(ddl);
}

void TableWindow::updateNewTableState()
{
    for (QWidget* tab : {ui->dataTab, ui->constraintsTab, ui->indexesTab, ui->triggersTab})
        ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(tab), existingTable);

    actionMap[EXPORT]->setEnabled(existingTable);
    actionMap[IMPORT]->setEnabled(existingTable);
    actionMap[POPULATE]->setEnabled(existingTable);
    actionMap[CREATE_SIMILAR]->setEnabled(existingTable);
    actionMap[RESET_AUTOINCREMENT]->setEnabled(existingTable);
    actionMap[REFRESH_STRUCTURE]->setEnabled(existingTable);
}

void TableWindow::addConstraint()
{
    addConstraint(ConstraintDialog::UNKNOWN);
}

void TableWindow::editConstraint()
{
    QModelIndex idx = ui->tableConstraintsView->currentIndex();
    editConstraint(idx);
}

void TableWindow::delConstraint()
{
    QModelIndex idx = ui->tableConstraintsView->currentIndex();
    delConstraint(idx);
}

void TableWindow::editConstraint(const QModelIndex& idx)
{
    if (!idx.isValid())
    {
        addConstraint();
        return;
    }

    SqliteCreateTable::Constraint* constr = structureConstraintsModel->getConstraint(idx.row());
    ConstraintDialog dialog(ConstraintDialog::EDIT, constr, createTable.data(), db, this);
    if (dialog.exec() != QDialog::Accepted)
        return;

    structureConstraintsModel->constraintModified(idx.row());
    ui->tableConstraintsView->resizeColumnToContents(0);
    ui->tableConstraintsView->resizeColumnToContents(1);
}

void TableWindow::delConstraint(const QModelIndex& idx)
{
    if (!idx.isValid())
        return;

    SqliteCreateTable::Constraint* constr = structureConstraintsModel->getConstraint(idx.row());

    QString arg = constr->name.isNull() ? constr->typeString() : constr->name;
    QString msg = tr("Are you sure you want to delete table constraint '%1'?", "table window").arg(arg);
    int btn = QMessageBox::question(this, tr("Delete constraint", "table window"), msg);
    if (btn != QMessageBox::Yes)
        return;

    structureConstraintsModel->delConstraint(idx.row());
    ui->structureView->resizeColumnToContents(0);
    updateTableConstraintsToolbarState();
}

void TableWindow::moveConstraintUp()
{
    QModelIndex idx = ui->tableConstraintsView->currentIndex();
    if (!idx.isValid())
        return;

    structureConstraintsModel->moveConstraintUp(idx.row());
    updateTableConstraintsToolbarState();
    updateStructureCommitState();
}

void TableWindow::moveConstraintDown()
{
    QModelIndex idx = ui->tableConstraintsView->currentIndex();
    if (!idx.isValid())
        return;

    structureConstraintsModel->moveConstraintDown(idx.row());
    updateTableConstraintsToolbarState();
    updateStructureCommitState();
}

void TableWindow::addPk()
{
    addConstraint(ConstraintDialog::PK);
}

void TableWindow::addFk()
{
    addConstraint(ConstraintDialog::FK);
}

void TableWindow::addUnique()
{
    addConstraint(ConstraintDialog::UNIQUE);
}

void TableWindow::addCheck()
{
    addConstraint(ConstraintDialog::CHECK);
}

void TableWindow::exportTable()
{
    if (!ExportManager::isAnyPluginAvailable())
    {
        notifyError(tr("Cannot export, because no export plugin is loaded."));
        return;
    }

    ExportDialog dialog(this);
    dialog.setTableMode(db, table);
    dialog.exec();
}

void TableWindow::importTable()
{
    if (!ImportManager::isAnyPluginAvailable())
    {
        notifyError(tr("Cannot import, because no import plugin is loaded."));
        return;
    }

    ImportDialog dialog(this);
    dialog.setDbAndTable(db, table);
    if (dialog.exec() == QDialog::Accepted && dataLoaded)
        ui->dataView->refreshData();
}

void TableWindow::populateTable()
{
    PopulateDialog dialog(this);
    dialog.setDbAndTable(db, table);
    if (dialog.exec() == QDialog::Accepted && dataLoaded)
        ui->dataView->refreshData();
}

void TableWindow::createSimilarTable()
{
    DbObjectDialogs dialog(db);
    dialog.addTableSimilarTo(QString(), table);
}

void TableWindow::tabChanged(int newTab)
{
    if (tabsMoving)
        return;

    if (newTab == getDataTabIdx())
    {
        if (isModified())
        {
            int res = QMessageBox::question(this, tr("Uncommitted changes"),
                                            tr("There are uncommitted structure modifications. You cannot browse or edit data until you have "
                                               "table structure settled.\n"
                                               "Do you want to commit the structure, or do you want to go back to the structure tab?"),
                                            tr("Go back to structure tab"), tr("Commit modifications and browse data."));

            ui->tabWidget->setCurrentIndex(0);
            if (res == 1)
                commitStructure(true);

            return;
        }

        if (!dataLoaded)
            ui->dataView->refreshData();
    }
}

void TableWindow::structureViewDoubleClicked(const QModelIndex &index)
{
    editColumn(index);
}

void TableWindow::constraintsViewDoubleClicked(const QModelIndex &index)
{
    editConstraint(index);
}

void TableWindow::nameChanged()
{
    if (!createTable)
        return;

    createTable->table = ui->tableNameEdit->text();
    updateDdlTab();
}

void TableWindow::withOutRowIdChanged()
{
    if (!createTable)
        return;

    createTable->withOutRowId = ui->withoutRowIdCheck->isChecked() ? QStringLiteral("ROWID") : QString();
    updateDdlTab();
    emit modifyStatusChanged();
}

void TableWindow::addIndex()
{
    DbObjectDialogs dialogs(db, this);
    dialogs.addIndex(table);
    updateIndexes();
}

void TableWindow::editCurrentIndex()
{
    QString index = getCurrentIndex();
    if (index.isNull())
        return;

    DbObjectDialogs dialogs(db, this);
    dialogs.editIndex(index);
    updateIndexes();
}

void TableWindow::indexViewDoubleClicked(const QModelIndex& idx)
{
    if (!idx.isValid())
    {
        addIndex();
        return;
    }

    QString index = ui->indexList->item(idx.row(), 0)->text();

    DbObjectDialogs dialogs(db, this);
    dialogs.editIndex(index);
    updateIndexes();
}


void TableWindow::delIndex()
{
    QString index = getCurrentIndex();
    if (index.isNull())
        return;

    DbObjectDialogs dialogs(db, this);
    dialogs.dropObject(index);
    updateIndexes();
}

void TableWindow::addTrigger()
{
    DbObjectDialogs dialogs(db, this);
    dialogs.addTriggerOnTable(table);
    updateTriggers();
}

void TableWindow::editTrigger()
{
    QString trigger = getCurrentTrigger();
    if (trigger.isNull())
    {
        addTrigger();
        return;
    }

    DbObjectDialogs dialogs(db, this);
    dialogs.editTrigger(trigger);
    updateTriggers();
}

void TableWindow::triggerViewDoubleClicked(const QModelIndex& idx)
{
    if (!idx.isValid())
    {
        addTrigger();
        return;
    }

    QString trigger = ui->triggerList->item(idx.row(), 0)->text();

    DbObjectDialogs dialogs(db, this);
    dialogs.editTrigger(trigger);
    updateTriggers();
}

void TableWindow::delTrigger()
{
    QString trigger = getCurrentTrigger();
    if (trigger.isNull())
        return;

    DbObjectDialogs dialogs(db, this);
    dialogs.dropObject(trigger);
    updateTriggers();
}

void TableWindow::updateIndexesState()
{
    bool editDel = ui->indexList->currentItem() != nullptr;
    actionMap[REFRESH_INDEXES]->setEnabled(existingTable);
    actionMap[ADD_INDEX]->setEnabled(existingTable);
    actionMap[EDIT_INDEX]->setEnabled(editDel);
    actionMap[DEL_INDEX]->setEnabled(editDel);
}

void TableWindow::updateTriggersState()
{
    bool editDel = ui->triggerList->currentItem() != nullptr;
    actionMap[REFRESH_TRIGGERS]->setEnabled(existingTable);
    actionMap[ADD_TRIGGER]->setEnabled(existingTable);
    actionMap[EDIT_TRIGGER]->setEnabled(editDel);
    actionMap[DEL_TRIGGER]->setEnabled(editDel);
}

void TableWindow::nextTab()
{
    int idx = ui->tabWidget->currentIndex();
    idx++;
    ui->tabWidget->setCurrentIndex(idx);
}

void TableWindow::prevTab()
{
    int idx = ui->tabWidget->currentIndex();
    idx--;
    ui->tabWidget->setCurrentIndex(idx);
}

void TableWindow::updateIndexes()
{
    ui->indexList->clear();

    if (!db || !db->isValid())
        return;

    SchemaResolver resolver(db);
    resolver.setIgnoreSystemObjects(true);
    QList<SqliteCreateIndexPtr> indexes = resolver.getParsedIndexesForTable(database, table);

    ui->indexList->setColumnCount(4);
    ui->indexList->setRowCount(indexes.size());
    ui->indexList->setHorizontalHeaderLabels({
                                                 tr("Name", "table window indexes"),
                                                 tr("Unique", "table window indexes"),
                                                 tr("Columns", "table window indexes"),
                                                 tr("Partial index condition", "table window indexes"),
                                             });

    Dialect dialect= db->getDialect();
    if (dialect == Dialect::Sqlite2)
        ui->indexList->setColumnCount(3);

    ui->indexList->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);

    QTableWidgetItem* item = nullptr;
    int row = 0;
    for (SqliteCreateIndexPtr index : indexes)
    {
        item = new QTableWidgetItem(index->index);
        item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
        ui->indexList->setItem(row, 0, item);

        // TODO a delegate to make the checkbox in the center, or use setCellWidget()
        item = new QTableWidgetItem();
        item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
        item->setCheckState(index->uniqueKw ? Qt::Checked : Qt::Unchecked);
        ui->indexList->setItem(row, 1, item);

        item = new QTableWidgetItem(indexColumnTokens(index).detokenize());
        item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
        ui->indexList->setItem(row, 2, item);

        if (dialect == Dialect::Sqlite3)
        {
            item = new QTableWidgetItem(index->where ? index->where->detokenize() : "");
            item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
            ui->indexList->setItem(row, 3, item);
        }

        row++;
    }

    ui->indexList->resizeColumnsToContents();
    ui->indexList->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);
    updateIndexesState();
}

void TableWindow::updateTriggers()
{
    if (!db || !db->isValid())
        return;

    SchemaResolver resolver(db);
    QList<SqliteCreateTriggerPtr> triggers = resolver.getParsedTriggersForTable(database, table);

    ui->triggerList->setColumnCount(4);
    ui->triggerList->setRowCount(triggers.size());
    ui->triggerList->horizontalHeader()->setMaximumSectionSize(200);
    ui->triggerList->setHorizontalHeaderLabels({
                                                 tr("Name", "table window triggers"),
                                                 tr("Event", "table window triggers"),
                                                 tr("Condition", "table window triggers"),
                                                 tr("Details", "table window triggers")
                                             });

    QTableWidgetItem* item = nullptr;
    QString timeAndEvent;
    int row = 0;
    for (SqliteCreateTriggerPtr trig : triggers)
    {
        item = new QTableWidgetItem(trig->trigger);
        item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
        ui->triggerList->setItem(row, 0, item);

        timeAndEvent = trig->tokensMap["trigger_time"].detokenize() + trig->tokensMap["trigger_event"].detokenize();
        item = new QTableWidgetItem(timeAndEvent);
        item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
        ui->triggerList->setItem(row, 1, item);

        item = new QTableWidgetItem(trig->precondition ? trig->precondition->detokenize().trimmed() : "");
        item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
        ui->triggerList->setItem(row, 2, item);

        item = new QTableWidgetItem(trig->tokensMap["trigger_cmd_list"].detokenize().trimmed());
        item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
        ui->triggerList->setItem(row, 3, item);

        row++;
    }

    ui->triggerList->resizeColumnsToContents();
    ui->triggerList->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch);
    updateTriggersState();
}

void TableWindow::editColumn(const QString& columnName)
{
    QModelIndex colIdx = structureModel->findColumn(columnName);
    if (!colIdx.isValid())
        return;

    editColumn(colIdx);
}

void TableWindow::delColumn(const QString& columnName)
{
    QModelIndex colIdx = structureModel->findColumn(columnName);
    if (!colIdx.isValid())
        return;

    delColumn(colIdx);
}

void TableWindow::updateTabsOrder()
{
    tabsMoving = true;
    ui->tabWidget->removeTab(getDataTabIdx());
    int idx = 1;
    if (CFG_UI.General.DataTabAsFirstInTables.get())
        idx = 0;

    ui->tabWidget->insertTab(idx, ui->dataTab, tr("Data"));
    tabsMoving = false;
}

bool TableWindow::restoreSessionNextTime()
{
    return existingTable && db && !DBLIST->isTemporary(db);
}

QToolBar* TableWindow::getToolBar(int toolbar) const
{
    switch (static_cast<ToolBar>(toolbar))
    {
        case TOOLBAR_STRUCTURE:
            return ui->structureToolBar;
        case TOOLBAR_INDEXES:
            return ui->indexToolBar;
        case TOOLBAR_TRIGGERS:
            return ui->triggerToolBar;
    }
    return nullptr;
}

bool TableWindow::handleInitialFocus()
{
    if (!existingTable)
    {
        ui->tableNameEdit->setFocus();
        return true;
    }
    return false;
}

bool TableWindow::isUncommitted() const
{
    return ui->dataView->isUncommitted() || isModified();
}

QString TableWindow::getQuitUncommittedConfirmMessage() const
{
    QString title = getMdiWindow()->windowTitle();
    if (ui->dataView->isUncommitted() && isModified())
        return tr("Table window \"%1\" has uncommitted structure modifications and data.").arg(title);
    else if (ui->dataView->isUncommitted())
        return tr("Table window \"%1\" has uncommitted data.").arg(title);
    else if (isModified())
        return tr("Table window \"%1\" has uncommitted structure modifications.").arg(title);
    else
    {
        qCritical() << "Unhandled message case in TableWindow::getQuitUncommittedConfirmMessage().";
        return QString();
    }
}

void TableWindow::useCurrentTableAsBaseForNew()
{
    newTable();
    ui->tableNameEdit->clear();
    updateWindowTitle();
    ui->tableNameEdit->setFocus();
    updateAfterInit();
}

Db* TableWindow::getAssociatedDb() const
{
    return db;
}

void TableWindow::updateFont()
{
    QFont f = CFG_UI.Fonts.DataView.get();
    QFontMetrics fm(f);

    QTableView* views[] = {ui->structureView, ui->tableConstraintsView, ui->indexList, ui->triggerList};
    for (QTableView* view : views)
    {
        view->setFont(f);
        view->horizontalHeader()->setFont(f);
        view->verticalHeader()->setFont(f);
        view->verticalHeader()->setDefaultSectionSize(fm.height() + 4);
    }
}
